package com.hup.utils.algorithm.image;

import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import java.awt.image.BufferedImage;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;

/**
 * 高斯模糊算法;
 * <pre>
 * 处理速度较慢!已使用3线程处理
 * 可调参数[sigma],未能实现PS中[半径]可调的功能
 *
 * 资料:
 * https://github.com/imageprocessor/cv4j/tree/master/cv4j/src/main/java/com/cv4j/core/filters
 * https://www.jianshu.com/p/41838f2d35c4
 * https://baike.baidu.com/item/%E9%AB%98%E6%96%AF%E6%A8%A1%E7%B3%8A
 * </pre>
 *
 * @author hugan
 * @date 2020/5/20
 */
@Log4j2
public class GaussianBlur {

    private final ExecutorService executor;
    /**
     * 高斯矩阵
     */
    private float[] kernel;
    /**
     * σ,正态分布的标准偏差;
     * 范围:(0,n];
     */
    @Setter
    private double sigma = 20;

    public GaussianBlur() {
        executor = ThreadPoolUtil.fixedThreadExecutor("GaussianBlur-", 3, "高斯模糊");
    }

    /**
     * 获取模糊后的图像;
     * 可调参数: {@link #setSigma(double)}
     *
     * @apiNote 线程不安全的!
     */
    @SuppressWarnings({"SuspiciousNameCombination"})
    public BufferedImage blur(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();
        makeGaussianKernel(sigma, 0.002, Math.min(width, height));

        int[] rgb = image.getRGB(0, 0, width, height, null, 0, width);
        int size = width * height;
        byte[] r = new byte[size];
        byte[] g = new byte[size];
        byte[] b = new byte[size];
        for (int i = 0; i < size; i++) {
            r[i] = (byte) ((rgb[i] & 0xff0000) >> 16);
            g[i] = (byte) ((rgb[i] & 0xff00) >> 8);
            b[i] = (byte) (rgb[i] & 0xff);
        }

        log.debug("开始处理");
        byte[][] channels = new byte[][]{r, g, b};
        CountDownLatch latch = new CountDownLatch(3);
        for (byte[] channel : channels) {
            executor.submit(() -> {
                byte[] temp = new byte[size];
                blur(channel, temp, width, height); // H Gaussian
                blur(temp, channel, height, width); // V Gaussain
                latch.countDown();
                log.debug("通道处理结束");
            });
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new IllegalArgumentException("线程中断异常");
        }
        log.debug("构造结果");
        //for (int i = 0; i < rgb.length - width * 200; i++) {//test
        for (int i = 0; i < rgb.length; i++) {
            int a = (rgb[i] >> 24) & 0xff;
            int r2 = Byte.toUnsignedInt(r[i]);
            int g2 = Byte.toUnsignedInt(g[i]);
            int b2 = Byte.toUnsignedInt(b[i]);
            rgb[i] = ((a & 0xff) << 24) | ((r2 & 0xff) << 16) | ((g2 & 0xff) << 8) | ((b2 & 0xff));
        }
        BufferedImage res = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        res.setRGB(0, 0, width, height, rgb, 0, width);
        return res;
    }

    /**
     * 释放内存:关闭异常执行器;
     * 执行后,将不能再次使用本类
     */
    public void destroy() {
        executor.shutdown();
    }

    /**
     * 初始化高斯矩阵
     */
    private void makeGaussianKernel(final double sigma, final double accuracy, int maxRadius) {
        int kRadius = (int) Math.ceil(sigma * Math.sqrt(-2 * Math.log(accuracy))) + 1;
        if (maxRadius < 50) maxRadius = 50;         // too small maxRadius would result in inaccurate sum.
        if (kRadius > maxRadius) kRadius = maxRadius;
        kernel = new float[kRadius];
        for (int i = 0; i < kRadius; i++)               // Gaussian function
            kernel[i] = (float) (Math.exp(-0.5 * i * i / sigma / sigma));
        double sum;                                 // sum over all kernel elements for normalization
        if (kRadius < maxRadius) {
            sum = kernel[0];
            for (int i = 1; i < kRadius; i++)
                sum += 2 * kernel[i];
        } else
            sum = sigma * Math.sqrt(2 * Math.PI);

        for (int i = 0; i < kRadius; i++) {
            double v = (kernel[i] / sum);
            kernel[i] = (float) v;
        }
    }

    /**
     * 实现色值模糊
     */
    private void blur(byte[] inPixels, byte[] outPixels, int width, int height) {
        for (int row = 0; row < height; row++) {
            int index = row;
            for (int col = 0; col < width; col++) {
                float sum = 0;
                for (int m = -(kernel.length - 1); m < kernel.length; m++) {
                    int subCol = col + m;
                    if (subCol < 0 || subCol >= width) {
                        subCol = 0;
                    }
                    int index2 = row * width + subCol;
                    int c = inPixels[index2] & 0xff;
                    sum += c * kernel[Math.abs(m)];
                }
                outPixels[index] = clamp(sum);
                index += height;
            }
        }
    }

    private byte clamp(float v) {
        if (v < 0) {
            return 0;
        } else if (v > 255) {
            return (byte) 255;
        } else {
            return (byte) v;
        }
    }

}
